package com.ruoyi.demo.util;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class EsUtil {

    @Qualifier("highLevelClient")
    @Autowired
    RestHighLevelClient highLevelClient;

    private static RestHighLevelClient client;

    @PostConstruct
    public void init() {
        client = this.highLevelClient;
    }

    public final static String IK_SMART = "ik_smart";

    public final static String IK_MAX_WORD = "ik_max_word";

    /**
     * 创建索引
     *
     * @param index 索引
     * @return
     * @throws IOException
     */
    @SneakyThrows
    public static boolean createIndex(String index) {
        if (existIndex(index)) {
            return true;
        }
        //创建索引请求
        CreateIndexRequest indexRequest = new CreateIndexRequest(index);
        /*//设置分片和副本数
        indexRequest.settings(Settings.builder()
                .put("index.number_of_shards", 5)
                .put("index.number_of_replicas", 1)
        );
        //创建映射，中间填写需要存到ES上的JavaDTO对象对应的JSON数据。
        indexRequest.mapping("topic",XContentType.JSON);*/
        CreateIndexResponse createIndexResponse = client.indices().create(indexRequest, RequestOptions.DEFAULT);
        return createIndexResponse.isAcknowledged();
    }

    /**
     * 创建索引并对某个字段进行ik分词
     *
     * @param index    索引
     * @param key      字段
     * @param analyzer 分词方式 粗粒的：ik_smart、细粒度：ik_max_word
     * @return
     * @throws IOException
     */
    public static boolean createIndex(String index, String key, String analyzer) throws IOException {
        if (existIndex(index)) {
            return true;
        }
        CreateIndexRequest indexRequest = new CreateIndexRequest(index);
        client.indices().create(indexRequest, RequestOptions.DEFAULT);

        PutMappingRequest request = new PutMappingRequest(index);
        request.source(getObjectMapping(key, analyzer));
        AcknowledgedResponse acknowledgedResponse = client.indices().putMapping(request, RequestOptions.DEFAULT);
        return acknowledgedResponse.isAcknowledged();
    }

    /**
     * 对某个字段设置ik分词器
     *
     * @param key      字段
     * @param analyzer 分词方式 粗粒的：ik_smart、细粒度：ik_max_word
     * @return
     */
    public static XContentBuilder getObjectMapping(String key, String analyzer) {
        XContentBuilder builder = null;
        try {
            builder = XContentFactory.jsonBuilder();
            builder.startObject();
            {
                builder.startObject("properties");
                {
                    builder.startObject(key);
                    {
                        builder.field("type", "text");
                        builder.field("analyzer", analyzer);
                    }
                    builder.endObject();
                }
                builder.endObject();
            }
            builder.endObject();
        } catch (IOException e) {
            log.error(e.toString());
        }
        return builder;
    }

    /**
     * 删除索引
     *
     * @param index 索引
     * @return
     * @throws IOException
     */
    @SneakyThrows
    public static boolean deleteIndex(String index) {
        DeleteIndexRequest request = new DeleteIndexRequest(index);
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        return response.isAcknowledged();
    }

    /**
     * 获取查询的总条数
     *
     * @param index         索引
     * @param startPage     起始页码
     * @param pageSize      条数
     * @param sourceBuilder 查询召回条件
     * @param queryBuilder  查询条件
     * @return
     */
    public static long countDataNumber(String index, int startPage, int pageSize, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder) {

        //组合返回条件
        SearchRequest request = getSearchRequest(index, startPage, pageSize, sourceBuilder, queryBuilder);

        try {
            //查询es
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return response.getHits().getTotalHits().value;
        } catch (IOException ioe) {
            log.error("条件查询失败，索引:{},异常信息{}", index, ioe);
        }
        return 0;
    }

    /**
     * 判断索引是否存在
     *
     * @param index 索引
     * @return
     * @throws IOException
     */
    public static boolean existIndex(String index) throws IOException {
        IndicesClient indices = client.indices();

        return indices.exists(new GetIndexRequest(index), RequestOptions.DEFAULT);
    }

    /**
     * es插入数据
     * @param index 索引
     * @param id    id
     * @param data  数据 （jsonString格式）
     */
    public static void insertData(String index, String id, String data) {
        //数据上传es
        try {
            BulkRequest request = new BulkRequest();
            request.add(
                    new IndexRequest(index)
                            .id(id)
                            .source(data, XContentType.JSON));
            BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
            int status = bulkResponse.status().getStatus();
            log.info("es插入数据完成，索引:{},id:{},status:{}", index, id, status);
        } catch (Exception e) {
            log.error("es插入数据失败，索引:{},id:{},异常信息:{}", index, id, e);
        }
    }

    /**
     * es批量插入数据
     *
     * @param index 索引
     * @param list  数据集合
     */
    public static <T> void insetBach(String index, List<T> list) {
        try {
            BulkRequest request = new BulkRequest();
            list.forEach(item ->
                    request
                            .add(new IndexRequest(index)
                                    .source(JSON.toJSONString(item), XContentType.JSON))
            );
            BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
            int status = bulkResponse.status().getStatus();
            log.info("批量插入数据完成，索引:{},status:{}", index, status);
        } catch (Exception e) {
            log.error("批量插入数据失败，索引:{},异常信息:{}", index, e);
        }

    }

    /**
     * 查询数据
     *
     * @param index         索引
     * @param startPage     起始页码
     * @param pageSize      条数
     * @param sourceBuilder 查询召回条件
     * @param queryBuilder  查询条件
     * @return
     */
    public static List<Map<String, Object>> searchData(String index, int startPage, int pageSize, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder) {

        //组合返回条件
        SearchRequest request = getSearchRequest(index, startPage, pageSize, sourceBuilder, queryBuilder);

        try {
            //查询es
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            long totalHits = response.getHits().getTotalHits().value;
            log.info("查询总条数：{}", totalHits);

            int status = response.status().getStatus();
            if (status == 200) {
                List<Map<String, Object>> sourceList = new ArrayList<>();
                for (SearchHit searchHit : response.getHits().getHits()) {
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    sourceList.add(sourceAsMap);
                }
                return sourceList;
            }
        } catch (IOException ioe) {
            log.error("条件查询失败，索引:{},异常信息:{}", index, ioe);
        }
        return null;
    }

    /**
     * 查询数据返回分页
     *
     * @param index         索引
     * @param startPage     起始页码
     * @param pageSize      条数
     * @param sourceBuilder 查询召回条件
     * @param queryBuilder  查询条件
     * @param clazz
     * @return
     */
    public static <T> Page<T> searchDataPage(String index, int startPage, int pageSize, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder, Class<T> clazz) {

        //组合返回条件
        SearchRequest request = getSearchRequest(index, startPage, pageSize, sourceBuilder, queryBuilder);

        try {
            //查询es
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            long totalHits = response.getHits().getTotalHits().value;
            log.info("查询总条数：{}", totalHits);

            int status = response.status().getStatus();
            if (status == 200) {
                List<T> sourceList = new ArrayList<>();
                for (SearchHit searchHit : response.getHits().getHits()) {
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    T t = JSON.parseObject(JSON.toJSONString(sourceAsMap), clazz);
                    sourceList.add(t);
                }
                Page page = new Page();
                page.setSize(pageSize);
                page.setPages((int) totalHits % pageSize == 0 ? (int) totalHits / pageSize : (int) totalHits / pageSize + 1);
                page.setCurrent(startPage);
                page.setTotal(totalHits);
                page.setRecords(sourceList);
                return page;
            }
        } catch (IOException ioe) {
            log.error("条件查询失败，索引:{},异常信息:{}", index, ioe);
        }
        return null;
    }

    /**
     * 查询数据
     *
     * @param index         索引
     * @param startPage     起始页码
     * @param pageSize      条数
     * @param sourceBuilder 查询召回条件
     * @param queryBuilder  查询条件
     * @param clazz         数据返回类型
     * @return
     */
    public static <T> List<T> searchData(String index, int startPage, int pageSize, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder, Class<T> clazz) {

        // 组合返回条件
        SearchRequest request = getSearchRequest(index, startPage, pageSize, sourceBuilder, queryBuilder);

        // 查询数据
        return getSearchData(index, clazz, request);
    }

    /**
     * 组合返回条件
     */
    public static SearchRequest getSearchRequest(String index, int startPage, int pageSize, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder) {
        SearchRequest request = new SearchRequest(index);
        // 设置超时时间
        sourceBuilder.timeout(new TimeValue(100, TimeUnit.SECONDS));
        // 设置是否按匹配度排序
        sourceBuilder.explain(true);
        // 加载查询条件
        sourceBuilder.query(queryBuilder);
        // 设置分页
        sourceBuilder.from((startPage - 1) * pageSize).size(pageSize);
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name", 10)
                .preTags("<font color='red'>")
                .postTags("</font>");

        sourceBuilder.highlighter(highlightBuilder);
        sourceBuilder.trackTotalHits(true);
        log.info("查询条件：{}", sourceBuilder.toString());

        request.source(sourceBuilder);

        return request;
    }


    /**
     * 组合返回条件
     */
    public static SearchResponse getSearchResponse(String index, int startPage, int pageSize, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder) {
        SearchRequest request = new SearchRequest(index);
        // 设置超时时间
        sourceBuilder.timeout(new TimeValue(100, TimeUnit.SECONDS));
        // 设置是否按匹配度排序
        sourceBuilder.explain(true);
        // 加载查询条件
        sourceBuilder.query(queryBuilder);
        // 设置分页
        sourceBuilder.from((startPage - 1) * pageSize).size(pageSize);
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name", 10)
                .preTags("<font color='red'>")
                .postTags("</font>");

        sourceBuilder.highlighter(highlightBuilder);
        sourceBuilder.trackTotalHits(true);
        log.info("查询条件：{}", sourceBuilder.toString());

        request.source(sourceBuilder);

        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return response;
    }

    /**
     * 查询数据
     *
     * @param index         索引
     * @param sourceBuilder 查询召回条件
     * @param queryBuilder  查询条件
     * @param clazz         数据返回类型
     * @return
     */
    public static <T> List<T> searchData(String index, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder, Class<T> clazz) {

        // 组合返回条件
        SearchRequest request = getSearchRequest(index, sourceBuilder, queryBuilder);

        // 查询数据
        return getSearchData(index, clazz, request);
    }

    private static <T> List<T> getSearchData(String index, Class<T> clazz, SearchRequest request) {
        try {
            // 查询es
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            long totalHits = response.getHits().getTotalHits().value;
            log.info("查询总条数：{}", totalHits);

            int status = response.status().getStatus();
            if (status == 200) {
                List<T> sourceList = new ArrayList<>();
                for (SearchHit searchHit : response.getHits().getHits()) {
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    T t = JSON.parseObject(JSON.toJSONString(sourceAsMap), clazz);
                    sourceList.add(t);
                }
                return sourceList;
            }
        } catch (IOException ioe) {
            log.error("条件查询失败，索引:{},异常信息:{}", index, ioe);
        }
        return null;
    }

    /**
     * 组合返回条件
     */
    private static SearchRequest getSearchRequest(String index, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder) {
        SearchRequest request = new SearchRequest(index);
        // 设置超时时间
        sourceBuilder.timeout(new TimeValue(100, TimeUnit.SECONDS));
        // 设置是否按匹配度排序
        sourceBuilder.explain(true);
        // 设置查询1000条数据，默认只查询10条
        sourceBuilder.size(1000);
        // 加载查询条件
        sourceBuilder.query(queryBuilder);

        //取消查询1万条的限制
        sourceBuilder.trackTotalHits(true);
        log.info("查询条件：{}", sourceBuilder.toString());

        request.source(sourceBuilder);

        return request;
    }

    /**
     * 查询数据
     *
     * @param index         索引
     * @param sourceBuilder 返回给前端的字段
     * @param queryBuilder  查询条件
     * @return
     */
    public static Map<String, Object> searchData(String index, SearchSourceBuilder sourceBuilder, QueryBuilder queryBuilder) {
        // 设置超时时间
        sourceBuilder.timeout(new TimeValue(100, TimeUnit.SECONDS));
        // 设置是否按匹配度排序
        sourceBuilder.explain(true);
        // 加载查询条件
        sourceBuilder.query(queryBuilder);
        // 数据条数
        sourceBuilder.trackTotalHits(true);
        log.info("查询条件：{}", sourceBuilder.toString());

        SearchRequest request = new SearchRequest(index);
        request.source(sourceBuilder);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            long totalHits = response.getHits().getTotalHits().value;
            log.info("查询总条数：{}", totalHits);
            // 返回状态判断
            int status = response.status().getStatus();
            if (status == 200) {
                SearchHit[] hits = response.getHits().getHits();
                Map<String, Object> map = hits[0].getSourceAsMap();
                return map;
            }
        } catch (IOException ioe) {
            log.error("条件查询失败，索引:{},异常信息:{}", index, ioe);
        }
        return null;
    }

    /**
     * 根据id删除数据
     *
     * @param index 索引
     * @param id    id
     */
    public static String deleteById(String index, String id) {
        String state = null;
        DeleteRequest request = new DeleteRequest(index, id);

        try {
            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
            int status = response.status().getStatus();
            state = Integer.toString(status);
            log.info("根据id删除数据完成，index:{},id:{}", index, id);
        } catch (IOException ioe) {
            log.error("根据id删除数据失败index:{},id:{}", index, id);
        }
        return state;
    }

    /**
     * 根据条件删除数据
     *
     * @param index        索引
     * @param queryBuilder 删除条件
     */
    public static void deleteByQuery(String index, QueryBuilder queryBuilder) {
        DeleteByQueryRequest request = new DeleteByQueryRequest(index);
        request.setQuery(queryBuilder);
        // 删除最大行数
        request.setBatchSize(1000);
        try {
            client.deleteByQuery(request, RequestOptions.DEFAULT);
            log.info("根据条件删除数据完成,index:{}", index);
        } catch (IOException ioe) {
            log.error("根据条件删除数据失败,index:{}", index);
        }
    }

    /**
     * es文档数量统计
     *
     * @param index 索引
     * @return
     */
    public static long getDocumentCount(String index) {
        long totalHits = 0;
        try {
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //设置数据条数
            sourceBuilder.trackTotalHits(true);
            SearchRequest request = new SearchRequest(index);
            request.source(sourceBuilder);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            totalHits = response.getHits().getTotalHits().value;
            log.info("查询总条数：{}", totalHits);
        } catch (IOException e) {
            log.error("异常信息:", e);
        }
        return totalHits;
    }
}
